package game.snake;

import game.Field;
import game.Point;
import game.snake.direction.*;
import game.snake.state.*;

import java.io.Serializable;
import java.util.ArrayList;

/**
 * The snake of the game.
 * 
 * @author Sarun Wongtanakarn 5510546166
 * @author Apiwat Jierakulpanit 5510546212
 * @Version 2013.05.01
 */
public class Snake implements Serializable {
	/** Size of the snake. */
	private int size;
	/** Direction of the snake. */
	private Direction direction;
	/** The body of the snake. */
	private ArrayList<SnakeBody> body;
	/** Field of the game. */
	private Field field;
	/** State of the snake. */
	private SnakeState state;

	/** The constants of directions. */
	public final Direction LEFT;
	public final Direction RIGHT;
	public final Direction UP;
	public final Direction DOWN;

	/** The constants of states. */
	public final SnakeState NORMAL;
	public final SnakeState SLEEP;

	/**
	 * Creates a snake with giving point of head, size and field.
	 * 
	 * @param point
	 *            the point that the head of snake reference to
	 * @param sLength
	 *            the beginning size of the snake
	 * @param field
	 *            the field of the game
	 */
	public Snake(Point point, int sLength, Field field) {
		this.field = field;
		LEFT = new DirectionLeft(this);
		RIGHT = new DirectionRight(this);
		UP = new DirectionUp(this);
		DOWN = new DirectionDown(this);

		NORMAL = new NormalState(this);
		SLEEP = new SleepState(this);

		body = new ArrayList<SnakeBody>();
		state = NORMAL;
		direction = DOWN;
		body.add(new SnakeBody(point, DOWN));
		size = body.size();
		this.growth(sLength, field.POINT);
	}

	/**
	 * Grows the snake.
	 * 
	 * @param x
	 *            the number of growing
	 * @param point
	 *            the matrix of point to set the new SnakeBody
	 */
	public void growth(int x, Point[][] point) {
		for (int i = 1; i < x + 1; i++) {
			getTail().getDirection().growth(point);
			size++;
		}
	}

	/**
	 * Returns the direction of this Snake.
	 * 
	 * @return the current direction
	 */
	public Direction getDirection() {
		return direction;
	}

	/**
	 * Sets the direction of this Snake.
	 * 
	 * @param direction
	 *            the new direction for setting
	 */
	public void setDirection(Direction direction) {
		this.direction = direction;
	}

	/**
	 * Returns the body of this snake.
	 * 
	 * @return the list of SnakeBody
	 */
	public ArrayList<SnakeBody> getBody() {
		return body;
	}

	/**
	 * Returns the head of this snake.
	 * 
	 * @return the first SnakeBody of this snake
	 */
	public SnakeBody getHead() {
		return body.get(0);
	}

	/**
	 * Returns the tail of this snake.
	 * 
	 * @return the last SnakeBody of this snake
	 */
	public SnakeBody getTail() {
		return body.get(body.size() - 1);
	}

	/**
	 * Returns the field of this game.
	 * 
	 * @return field of this game
	 */
	public Field getField() {
		return this.field;
	}

	/**
	 * Moves the snake.
	 * Each of SnakeBodys move follow their direction.
	 * Changes the direction of all SnakeBody after moving.
	 */
	public void move() {
		for (SnakeBody c : body)
			c.getDirection().move(c, field);
		for (int i = body.size() - 1; i > 0; i--) {
			body.get(i).setDirection(body.get(i - 1).getDirection());
		}
		body.get(0).setDirection(direction);
	}

	/**
	 * Sets the state of this Snake.
	 * 
	 * @param state
	 *            the new state for setting
	 */
	public void setState(SnakeState state) {
		this.state = state;
	}

}
